package runner

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/gitee-go/core/bean/hbtpBean"
	"gitee.com/gitee-go/utils"
	"io"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"
	"sync"
	"time"

	"gitee.com/gitee-go/runner-core/runCloud"
	hbtp "github.com/mgr9525/HyperByte-Transfer-Protocol"

	"gitee.com/gitee-go/core"
	"gitee.com/gitee-go/core/common"
	"gitee.com/gitee-go/core/runtime"
	"gitee.com/gitee-go/runner-core/comm"
	"gitee.com/gitee-go/utils/ioex"
)

// ExecTask 任务的执行实体
type ExecTask struct {
	egn      *Manager
	job      *comm.RunJob
	Status   string
	Event    string
	Error    string
	ExitCode int

	stat  int //控制状态 0未运行,1运行中，2完成
	bngtm time.Time
	endtm time.Time

	wrkpth  string //工作地址
	repopth string //仓库地址

	cmdend   bool
	cmdctx   context.Context
	cmdcncl  context.CancelFunc
	cmdenv   utils.EnvVal
	cmdenvlk sync.RWMutex
}

// 开始执行任务
// 1.检查参数
// 2.检查运行状态
// 3.获取仓库文件
// 4.开始运行任务逻辑
func (c *ExecTask) start() {
	c.stat = 1
	c.bngtm = time.Now()
	c.wrkpth = filepath.Join(comm.MainCfg.Workspace, comm.PathTask, c.job.Id)
	c.repopth = filepath.Join(c.wrkpth, "repo")
	//c.shellpth=filepath.Join(c.wrkpth,"cmds")
	c.cmdctx, c.cmdcncl = context.WithCancel(c.egn.ctx)
	c.cmdend = false

	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("ExecTask start:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
			c.Status = common.BUILD_STATUS_ERROR
			c.Error = fmt.Sprintf("recover err:%v", err)
			c.update()
		}
		//os.RemoveAll(c.repopth)
	}()
	defer func() {
		c.stat = 2
		c.endtm = time.Now()
		c.egn.linelk.Lock()
		delete(c.egn.lines, c.job.Id)
		c.egn.linelk.Unlock()
		core.Log.Debugf("job end(%s) rms(%d):%s", c.job.Name, len(c.egn.lines), c.job.Id)
		os.RemoveAll(c.wrkpth)
	}()

	os.RemoveAll(c.wrkpth)
	os.MkdirAll(c.repopth, 0750)
	/*c.Status = common.BUILD_STATUS_READY
	c.update()*/
	if err := c.check(); err != nil {
		c.Status = common.BUILD_STATUS_ERROR
		c.Error = fmt.Sprintf("check err:%v", err)
		c.pushLogjson(&hbtpBean.CmdLogLineJson{
			Id:   utils.NewXid(),
			Type: hbtpBean.TypeCmdLogLineCmderr,
			//Name: "get depend artifact err",
			Content: fmt.Sprintf("build check param err:%v", err),
		})
		goto ends
	}
	if c.checkStop() {
		c.Status = common.BUILD_STATUS_ERROR
		c.Error = fmt.Sprintf("manual stop!!")
		c.pushLogjson(&hbtpBean.CmdLogLineJson{
			Id:   utils.NewXid(),
			Type: hbtpBean.TypeCmdLogLineCmderr,
			//Name: "get depend artifact err",
			Content: "manual stop",
		})
		goto ends
	}
	if err := c.getrepo(); err != nil {
		c.Status = common.BUILD_STATUS_ERROR
		c.Error = fmt.Sprintf("get repostiory err:%+v", err)
		c.pushLogjson(&hbtpBean.CmdLogLineJson{
			Id:   utils.NewXid(),
			Type: hbtpBean.TypeCmdLogLineCmderr,
			//Name: "get depend artifact err",
			Content: fmt.Sprintf("get repo err:%v", err),
		})
		goto ends
	}

	core.Log.Debugf("job exec(%s):%s", c.job.Name, c.job.Id)
	c.Status = common.BUILD_STATUS_RUNNING
	c.update()
	go c.run()
	for !ioex.CheckContext(c.egn.ctx) && !c.runCheck() {
		time.Sleep(time.Millisecond * 100)
	}

ends:
	c.update()
}

func (c *ExecTask) Stop() {
	if c.cmdcncl != nil {
		c.cmdcncl()
		c.cmdcncl = nil
	}
}
func (c *ExecTask) check() error {
	if c.job.Name == "" {
		//c.update(common.BUILD_STATUS_ERROR,"build Job name is empty")
		return errors.New("build Job name is empty")
	}
	return nil
}

// 循环更新状态直到server返回成功
func (c *ExecTask) update() {
	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("ExecTask update:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
		}
	}()
	for {
		if err := c.updates(); err != nil {
			core.Log.Errorf("ExecTask update err:%v", err)
		} else {
			break
		}
		time.Sleep(time.Millisecond * 100)
		if ioex.CheckContext(c.cmdctx) {
			break
		}
	}
}

// 调用server更新job状态
func (c *ExecTask) updates() error {
	code, bts, err := runCloud.DoString("JobUpdate", &runtime.ReqJobUpdate{
		Jobid:    c.job.Id,
		Status:   c.Status,
		Error:    c.Error,
		ExitCode: c.ExitCode,
	})
	if err != nil {
		return err
	}
	if code != hbtp.ResStatusOk {
		return fmt.Errorf("res code(%d) not ok:%s", code, string(bts))
	}
	return nil
}

// 获取仓库代码文件
func (c *ExecTask) getrepo() error {
	req, err := runCloud.NewReq("DownBuildRepo")
	if err != nil {
		return err
	}
	defer req.Close()
	err = req.Do(nil, c.job.BuildId)
	if err != nil {
		return err
	}
	if req.ResCode() != hbtp.ResStatusOk {
		return fmt.Errorf(string(req.ResBodyBytes()))
	}
	sz := hbtp.BigByteToInt(req.ResBodyBytes())
	if sz <= 0 {
		return fmt.Errorf("down build repo size err:%d", sz)
	}
	flpth := filepath.Join(c.wrkpth, "repo.zip")
	os.RemoveAll(flpth)
	zipfl, err := os.OpenFile(flpth, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		return err
	}
	defer zipfl.Close()
	defer os.RemoveAll(flpth)
	bts := make([]byte, 1024)
	rn := int64(0)
	for {
		n, err := req.Conn().Read(bts)
		if n > 0 {
			rn += int64(n)
			zipfl.Write(bts[:n])
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
	}
	zipfl.Close()
	if rn != sz {
		return fmt.Errorf("network down repo size err:%d", sz)
	}

	core.Log.Debugf("getrepo down file size:%d,to:%s", sz, flpth)
	return ioex.UnZip(flpth, c.repopth)
}

// 检测server是否已经决定异常终止任务(封装)
func (c *ExecTask) runCheck() bool {
	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("ExecTask runLog:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
		}
	}()

	if c.checkStop() {
		c.Stop()
	}
	/*stat:=&runtime.BuildTaskStat{}
	err:=runCloud.DoJson("BuildStatus",c.job.BuildId,stat)
	if err != nil {
		core.Log.Errorf("ExecTask runLog err:%v", err)
		time.Sleep(time.Second)
		return c.cmdend
	}*/

	return c.cmdend
}

// 检测server是否已经决定异常终止任务
func (c *ExecTask) checkStop() bool {
	stat := &runtime.BuildTaskStat{}
	req, err := runCloud.DoJsons("BuildStatus", c.job.BuildId, stat)
	if err != nil {
		core.Log.Errorf("ExecTask checkStop err:%v", err)
		if req.ResCode() != hbtp.ResStatusNotFound {
			return true
		}
		return false
	}
	if stat.CtrlEnd {
		return true
	}
	return false
}

// 初始化运行环境变量
func (c *ExecTask) initCmdEnv() {
	c.cmdenv = utils.EnvVal{}
	for k, v := range c.egn.sysEnv {
		c.cmdenv[k] = v
	}
	for _, v := range comm.MainCfg.Env {
		i := strings.Index(v, "=")
		if i > 0 {
			k := v[:i]
			val := v[i+1:]
			if val != "" {
				c.cmdenv[k] = val
				core.Log.Debug("cmd env:", k, "=", val)
			}
		}
	}
	c.cmdenv["WORKPATH"] = c.wrkpth
}

// 开始执行job逻辑
// 1. 获取制品
// 2. 循环所有命令并执行
// 3. 命令执行完成后上传制品
func (c *ExecTask) run() {
	defer func() {
		c.cmdend = true
		if err := recover(); err != nil {
			core.LogPnc.Errorf("ExecTask runCmd:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
			c.Status = common.BUILD_STATUS_ERROR
		}
	}()

	if len(c.job.Commands) <= 0 {
		c.Status = common.BUILD_STATUS_OK
		return
	}
	// 获取制品(下载文件,注入环境变量)
	envs, err := c.getdepnarti()
	if err != nil {
		c.Status = common.BUILD_STATUS_ERROR
		c.Error = fmt.Sprintf("getdepnarti err:%+v", err)
		c.pushLogjson(&hbtpBean.CmdLogLineJson{
			Id:   utils.NewXid(),
			Type: hbtpBean.TypeCmdLogLineCmderr,
			//Name: "get depend artifact err",
			Content: "get depend artifact err", //fmt.Sprintf("get depend artifact err:%v",err),
		})
		return
	}

	c.initCmdEnv()
	for _, v := range c.job.Commands { // 一条命令一条命令的执行
		proc := &procExec{
			prt:  c,
			cmd:  v,
			envs: envs,
		}
		err = proc.start()
		if err != nil {
			if hbtp.EndContext(c.cmdctx) {
				c.Status = common.BUILD_STATUS_CANCEL
			} else {
				c.Status = common.BUILD_STATUS_ERROR
				c.Error = err.Error()
			}
			return
		}
	}

	err = c.genArtifact()
	if err != nil {
		core.Log.Errorf("genArtifact err:%v", err)
		c.Status = common.BUILD_STATUS_ERROR
		c.Error = fmt.Sprintf("genArtifact err:%v", err)
		c.pushLogjson(&hbtpBean.CmdLogLineJson{
			Id:   utils.NewXid(),
			Type: hbtpBean.TypeCmdLogLineCmderr,
			//Name: "get depend artifact err",
			Content: "put artifact err", //fmt.Sprintf("put artifact err:%v",err),
		})
		return
	}

	c.Status = common.BUILD_STATUS_OK
}

// 上传一条日志信息到当前job
func (c *ExecTask) pushLogjson(e *hbtpBean.CmdLogLineJson) error {
	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("ExecTask pushLogjson:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
		}
	}()
	e.Times = time.Now()
	core.Log.Debugf("ExecTask pushLogjson %s(%s):%s", e.Type, e.Gid, e.Content)
	hd := hbtp.Mp{
		"buildid": c.job.BuildId,
		"jobid":   c.job.Id,
	}
	code, bts, err := runCloud.DoString("JobLogJson", e, hd)
	if err != nil {
		core.Log.Debugf("cmdExec pushLogjson err:%v", err)
		return err
	}
	if code != hbtp.ResStatusOk {
		return fmt.Errorf("req err:%s", string(bts))
	}
	return nil
}
